-module(demo_counting).

% Erlang examples of "how to count".
%
% For experienced programmers in "normal" (non-functional) languages
% who are wondering how one can program without looping constructs 
% ("for", "do/while")
%
% Copyright (c) 2008 Matt Kangas
%
% $Id: demo_counting.erl 22 2008-02-18 02:11:15Z kangas $

-export([test/0]).

test() ->
	io:fwrite("How many ways can we count to 10?~n"),
	io:fwrite("-- 1 --~n"), counter1(),
	io:fwrite("~n-- 2 --~n"), counter2(),
	io:fwrite("~n-- 3 --~n"), counter3(),
	io:fwrite("~n-- 4 --~n"), counter4(),
	io:fwrite("~n-- 5 --~n"), counter5(),
	io:fwrite("~n-- 6 --~n"), counter6(),
	io:fwrite("~n-- 7 --~n"), counter7(),
	io:fwrite("~n-- 8 (backwards) --~n"), counter8(),
	io:fwrite("~n-- 9 --~n"), counter9(),
	io:fwrite("~n-- 10 --~n"), counter10(),
	
	io:fwrite("~n-- matrix1 --~n"), count_matrix1(),
	io:fwrite("~n-- matrix2 --~n"), count_matrix2(),
	io:fwrite("~n-- matrix3 --~n"), count_matrix3(),
	io:fwrite("~n-- matrix4 --~n"), count_matrix4(),
	io:fwrite("~n-- matrix5 --~n"), count_matrix5(),
	io:fwrite("~n-- matrix6 --~n"), count_matrix6(),
	io:fwrite("~n-- matrix7 --~n"), count_matrix7(),
	io:fwrite("~nHappy happy joy joy!~n").

% ----------------------------------------------
% Linear examples
% ----------------------------------------------

%% Example 1:
% Build a list, a print function, apply the function to the list
% lists:map() would work too. (It returns a value, lists:foreach() does not)
counter1() ->
	L = lists:seq(1,10),
	PrintInt = fun(I) -> io:fwrite("~p ", [I]) end,
	lists:foreach(PrintInt, L).


% Helper for below. "io:format()" and "io:fwrite()" are the same function
printInt(I) -> io:format("~p ", [I]).

% Example 2:
% Same as counter1, but use a function reference instead of an inline lambda
counter2() ->
	L = lists:seq(1,10),
	lists:foreach(fun printInt/1, L).


%% Example 3:
% Same as counter2, but use a list comprehension instead of lists:foreach/2
counter3() ->
	L = lists:seq(1,10),
	[ printInt(I) || I <- L].


%% Example 4: Recursion with an accumulator.
% Use a guard sequence ("when N > 10") to specify our base case (termination criteria).
% Very efficient, since it doesn't allocate a list of numbers, and is tail-recursive
%
% We define two functions, "counter4/0" (zero args) and "counter4/1".
% "counter4/1" consists of two clauses separated by ";"
counter4() -> counter4(1).

counter4(N) when N > 10 -> none;
counter4(N) ->
	printInt(N), counter4(N + 1).


%% Example 5: Recursive list processing.
% Nibble one item off the head of the list on each pass. 
% [H|T] works like car/cdr in lisp
counter5() -> 
	counter5( lists:seq(1,10) ).

counter5([]) -> none;
counter5([H|T]) -> 
	printInt(H), counter5(T).


%% Example 6: List processing with an accumulator
counter6() -> 
	%L = [1,1,1,1,1,1,1,1,1,1],
	L = lists:duplicate(10,1),
	counter6(L, 0).

counter6([], Acc) -> Acc;
counter6([H|T], Acc) ->
	N = H + Acc,
	printInt(N),
	counter6(T, N).


%% Example 7: Use lists:foldl(). Much cleaner than above!
counter7() -> 
	L = [1,1,1,1,1,1,1,1,1,1],
	F = fun(A, AccIn) -> 
		K = A + AccIn, printInt(K), K end,
	lists:foldl(F, 0, L).


%% Example 8: lists:foldr() – not tail-recursive, 
% therefore not to be used when foldl() will work instead.
% Note that the list comes out backwards :-)
counter8() -> 
	L = lists:seq(1,10),
	F = fun(A, AccIn) -> printInt(A), AccIn end,
	lists:foldr(F, void, L).


%% Example 9: Anonymous recursive function, per 
%	http://www.trapexit.org/forum/viewtopic.php?p=25927
% Disadvantages:
% - Need to pass the function as an argument to itself
% - Self recursive anonymous functions don't handle hot code updates
counter9() -> 
	F = fun(_,N) when N > 10 -> none;
		   (G,N) -> printInt(N), G(G,N+1)
		end,
	F(F,1).


%% Example 10:
% Use Y combinator to "simplify" anonymous recursion. Crazy? Yes! :-) 
% But what the hell...
% Recipe from http://bc.tech.coop/blog/070611.html
y(M) ->
    G = fun (F) -> M(fun(A) -> (F(F))(A) end) end,
    G(G).

counter10() -> 
	X = fun (F) ->
		fun (N) when N > 10 -> none;
			(N) -> printInt(N), F(N+1)
		end
	end,
	(y(X))(1).


% ----------------------------------------------
% Matrix examples
% ----------------------------------------------

%% Matrix Example 1: Recursive matrix processing.
%  5x5 matrix, zero-relative BUT it counts TOO FAR along the Y-axis
% --
count_matrix1() -> 
	count_matrix1( 5, 0, 0 ).

count_matrix1(Size, Size, Size) -> none;
count_matrix1(Size, Size, PtrY) -> 
	io:format("~n"),
	count_matrix1(Size, 0, PtrY+1);
count_matrix1(Size, PtrX, PtrY) -> 
	io:format("(~p,~p) ", [PtrX, PtrY]),
	count_matrix1(Size, PtrX+1, PtrY).

%% Matrix Example 2: Recursive matrix processing.
%  5x5 matrix, use guard clauses to fix problems above
% --
count_matrix2() -> 
	count_matrix2( 5, 1, 1 ).

count_matrix2(Size, _, PtrY) 	when (PtrY =:= Size+1) -> none;
count_matrix2(Size, PtrX, PtrY) when (PtrX =:= Size+1) -> 
	io:format("~n"),
	count_matrix2(Size, 1, PtrY+1);
count_matrix2(Size, PtrX, PtrY) -> 
	io:format("(~p,~p) ", [PtrX, PtrY]),
	count_matrix2(Size, PtrX+1, PtrY).

%% Matrix Example 3:
% Counts negative->positive values, otherwise similar as above.
% Wouldn't it be nice to abstract the print logic buried inside this loop?
% --
count_matrix3() -> count_matrix3( -3, 3 ).

count_matrix3(Min, Max) -> count_matrix3( Min, Max, Min, Min ).

count_matrix3(_Min, Max, _X, Y) when (Y =:= Max+1) -> none;
count_matrix3(Min, Max, X, Y) when (X =:= Max+1) -> 
	io:format("~n"),
	count_matrix3(Min, Max, Min, Y+1);
count_matrix3(Min, Max, X, Y) -> 
	io:format("(~p,~p) ", [X, Y]),
	count_matrix3(Min, Max, X+1, Y).

%% Matrix Example 4:
% Abstracts Min, Max, *AND* the print logic now! :-)
% This one handles non-symmetric min/max values, as we demonstrate
%
% Still feels a bit clunky. I don't like the "Max+1" to indicate EOL,
% --
count_matrix4() -> 
	Min = -1, Max = 5,
	F = fun (X,_Y) when X =:= Max+1 -> io:format("~n");
		(X,Y) -> io:format("(~p,~p) ", [X, Y])
	end,
	count_matrix4(F, Min, Max).

count_matrix4(F, Min, Max) -> count_matrix4(F, Min, Max, Min, Min ).

count_matrix4(_F, _Min, Max, _X, Y) when (Y =:= Max+1) -> none;
count_matrix4(F, Min, Max, X, Y) when (X =:= Max+1) -> 
	F(X,Y), count_matrix4(F, Min, Max, Min, Y+1);
count_matrix4(F, Min, Max, X, Y) -> 
	F(X,Y), count_matrix4(F, Min, Max, X+1, Y).

% =========================================================
% The following examples were generously provided by
% Philip Robinson via email, Jan 31 2008
%
% http://chlorophil.blogspot.com/
% clorophil-AT-gmail-DOT-com
%
% Thanks Philip!
% =========================================================

% Iterative, nested:
count_matrix5() ->
   L = lists:seq(-1, 5),
   lists:foreach(
       fun(Y) ->
           lists:foreach(fun(X) -> io:format("(~p,~p) ", [X, Y]) end, L),
           io:format("~n")
           end,
       L).

% Nested list comprehensions.
% (Inefficiently builds and discards the map result):
count_matrix6() ->
   L = lists:seq(-1, 5),
   [(fun(J) -> [(fun(I) -> io:format("(~p,~p) ", [I,J]) end)(X) || X
<- L], io:format("~n") end)(Y) || Y <- L],
   ok.

% Generically traverse an N-dimensional matrix.
% Axis ranges are given in reverse order (Z, Y, X...):
count_matrix7() ->
   count_matrix7([{-1,5},{-1,5}]),     % 2D matrix as above
   count_matrix7([{0,2},{0,3},{0,4}]). % 3D matrix

count_matrix7(Ranges) -> count_matrix7(Ranges, []).

count_matrix7([{Min,Max}|Ranges], Curr) ->
   lists:foreach(fun(C) -> count_matrix7(Ranges, [C|Curr]) end,
lists:seq(Min, Max)),
   io:format("~n");
count_matrix7([], Curr) -> count_matrix7_print(Curr).

count_matrix7_print([N]) -> io:format("(~p) ", [N]);
count_matrix7_print([N|Rest]) -> io:format("(~p", [N]),
count_matrix7_print2(Rest).

count_matrix7_print2([N|Rest]) -> io:format(",~p", [N]),
count_matrix7_print2(Rest);
count_matrix7_print2([]) -> io:format(") ").

